package cn.tannn.surenessdemo.sureness;

import cn.jdevelops.result.result.ResultVO;
import cn.tannn.surenessdemo.sureness.config.NoFilterPathConfig;
import com.alibaba.fastjson.JSON;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.usthe.sureness.mgt.SurenessSecurityManager;
import com.usthe.sureness.processor.exception.*;
import com.usthe.sureness.subject.SubjectSum;
import com.usthe.sureness.util.SurenessContextHolder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;

import javax.annotation.Resource;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Set;

import static cn.tannn.surenessdemo.util.CustomUtil.isOkPath;

/**
 * todo 权限认证第一步 (添加过滤器拦截所有请求,对所有请求进行认证鉴权)
 * @author tnnn
 */
@Order(1)
@WebFilter(filterName = "SurenessFilterExample", urlPatterns = "/*", asyncSupported = true)
public class SurenessFilter implements Filter {

    private static final Logger LOG = LoggerFactory.getLogger(SurenessFilter.class);

    /**
     * 放行 默认放行swagger，/user/login/**等路径
     */
    @Resource
    private NoFilterPathConfig noFilterPath;


    /**
     * 不需要拦截的地址
     */
    private  Set<String> addPathPatterns;

    /**
     * init 不需要拦截的常规uri
     * @param filterConfig filterConfig
     */
    @Override
    public void init(FilterConfig filterConfig) {
        addPathPatterns = noFilterPath.getExcludePathPatterns();
        // 初始化时读取web.xml中配置的init-param
        addPathPatterns.add("/swagger-resources/**");
        addPathPatterns.add("/webjars/**");
        addPathPatterns.add("/v2/api-docs/**");
        addPathPatterns.add("/v3/api-docs/**");
        addPathPatterns.add("/swagger**/**");
        addPathPatterns.add("/swagger-ui.html/**");
        addPathPatterns.add("/doc.html/**");
        addPathPatterns.add("/user/login/**");
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
            throws IOException, ServletException {

        // 放行不需要本权限处理的请求，如swagger，actuator 等，这些让他们自己的security去处理
        if (Boolean.FALSE.equals(isOkPath(addPathPatterns ,servletRequest))) {
            try {
                /*
                  鉴定用户的权限，状态等。如果鉴定不通过会抛出异常，针对不同的异常构建不同的错误信息体返回给调用者
                   1. 获取所有的 subject
                   2. 资源树权限匹配 DefaultPathRoleMatcher
                    2.1 判断是否可以放行资源（放行注解修饰,DatabasePathTreeProvider.provideExcludedResource获取的不拦截资源
                    2.2 根据每次请求时经过 SubjectCreator 中设置的TargetResource(当前请求地址===请求method) 在资源树中搜索对应权限
                        设置到 subject.setSupportRoles中，为Processor中的authorized的鉴权做数据准备( todo 考虑把这里抽象出来，内存资源树改成redi资源树)
                   3. 加载  subject 对应的 processor(验证处理器,下面时我自定义的redis验证器（CustomRedisProcessor）验证流程)
`                   3.1 token合法检验
                    3.2 token解析（过期也解析，我这边不用jwt的过期，用的redis中的过期策略
                    3.3 根据token中的subject(用户唯一值),进行redis过期验证
                    3.4 检验redis token跟 传入的token是否一致
                    3.5 检验用户状态（封禁，异常登录锁定
                    3.6 设置当前用户的权限到subject.setOwnRoles中，为Processor中的authorized的鉴权做数据准备
                    3.7 设置 subject.setPrincipalMap（jwt的json数据）
                    3.8 刷新用户的token（续签
                    3.9 刷新资源树
                    3.19 authorized 鉴权
                   4. 返回集合的subject  SubjectSum
                 */
                SubjectSum subject = SurenessSecurityManager.getInstance().checkIn(servletRequest);
                // 认证鉴权成功则会返回带用户信息的subject 可以将subject信息绑定到当前线程上下文holder供后面使用
                if (subject != null) {
                    SurenessContextHolder.bindSubject(subject);
                }
            } catch (ProcessorNotFoundException | UnknownAccountException | UnsupportedSubjectException e4) {
                // 账户创建相关异常
                responseWrite(ResponseEntity
                        .status(HttpStatus.BAD_REQUEST).body(e4.getMessage()), servletResponse);
                return;
            } catch (DisabledAccountException | ExcessiveAttemptsException e2 ) {
                // 账户禁用相关异常
                responseWrite(ResponseEntity
                        .status(HttpStatus.UNAUTHORIZED).body(e2.getMessage()), servletResponse);
                return;
            } catch (IncorrectCredentialsException | ExpiredCredentialsException e3) {
                // 认证失败相关异常
                responseWrite(ResponseEntity
                        .status(HttpStatus.UNAUTHORIZED).body(e3.getMessage()), servletResponse);
                return;
            } catch (NeedDigestInfoException e5) {
                // digest认证需要重试异常
                responseWrite(ResponseEntity
                        .status(HttpStatus.UNAUTHORIZED)
                        .header("WWW-Authenticate", e5.getAuthenticate()).build(), servletResponse);
                return;
            } catch (UnauthorizedException e6) {
                // 鉴权失败相关异常，即无权访问此api
                responseWrite(ResponseEntity
                        .status(HttpStatus.FORBIDDEN).body(e6.getMessage()), servletResponse);
                return;
            } catch (RuntimeException e) {
                // 其他异常
                responseWrite(ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(),
                        servletResponse);
                return;
            }
        }else {
            LOG.debug("放行url");
        }
        try {
            // 若未抛出异常 则认证鉴权成功 继续下面请求流程
            filterChain.doFilter(servletRequest, servletResponse);
        } finally {
            // 异常时清空 SurenessContextHolder
            SurenessContextHolder.clear();
        }
    }

    /**
     * 构建发生异常时返回的对象
     * @param content content
     * @param response response
     */
    private static void responseWrite(ResponseEntity content, ServletResponse response) {
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json;charset=utf-8");
        ((HttpServletResponse)response).setStatus(content.getStatusCodeValue());
        content.getHeaders().forEach((key, value) ->
                ((HttpServletResponse) response).addHeader(key, value.get(0)));
        try (PrintWriter printWriter = response.getWriter()) {
            if (content.getBody() != null) {
                ResultVO<Object> fail;
                if (content.getBody() instanceof String) {
                    fail = ResultVO.fail(content.getStatusCode().value(),content.getBody().toString());
                } else {
                    ObjectMapper objectMapper = new ObjectMapper();
                    fail = ResultVO.fail(content.getStatusCode().value(),objectMapper.writeValueAsString(content.getBody()));
                }
                printWriter.write(JSON.toJSONString(fail));
            } else {
                printWriter.write(JSON.toJSONString(ResultVO.fail("token是非法的")));
                printWriter.flush();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }



}
